// ==UserScript==
// @name         kk-copy-helper
// @namespace    https://greasyfork.org/zh-CN/users/1167332-stephenykk
// @homepage    https://greasyfork.org/zh-CN/scripts/514711-kk-copy-helper
// @sourcecode    https://gitee.com/stephenykk/tampermonkey-copy-helper.git
// @version      1.0.37
// @description  文章拷贝助手，掘金、简书、微信文章、知乎专栏、思否、CSDN、新华网、人民网、 文章一键拷贝 markdown，欢迎关注 前端公众号：JS酷
// @author       #前端公众号：JS酷
// @match        https://juejin.cn/post/*
// @match        https://blog.csdn.net/*/article/details/*
// @match        https://www.jianshu.com/p/*
// @match        https://www.cnblogs.com/*/p/*.html
// @match        https://segmentfault.com/a/*
// @match        https://mp.weixin.qq.com/s*
// @match        https://www.ruanyifeng.com/blog/*
// @match        https://zhuanlan.zhihu.com/p/*
// @match        https://sspai.com/post/*
// @match        https://panzhenjie.fun/*
// @match        http://localhost:4000/*
// @match        *://www.news.cn/*/**/*.htm*
// @match        *://*.people.com.cn/*/**/*.htm*
// @icon         https://res.wx.qq.com/a/fed_upload/9300e7ac-cec5-4454-b75c-f92260dd5b47/logo-mp.ico
// @grant        none
// @license MIT
// ==/UserScript==

import get from "lodash.get";
import { themes } from "./themes";
import { turndown } from "./turndown";
import { processDocument } from "./playform";
import {
  solveWeChatMath,
  solveHtml,
  solveZhihuMath,
  downLoad,
  copySafari,
} from "./utils";
__webpack_nonce__ = "index";
import "./styles/index.css";
__webpack_nonce__ = "base";
import "./styles/mdnice/base.css";
__webpack_nonce__ = "hljs";
import "./styles/mdnice/hljs.css";

const myDomain = "panzhenjie.fun";
const hexoPort = 4000;
const copyHelperDevPort = 8000;
const localhostPorts = [hexoPort, copyHelperDevPort];

/**
 * 增加支持站点的方法
 * 1. 新站点文章的选择器，加入 selector 对象
 * 2. 在当前文件头部设置 @match 指令
 * 3. src/playform 增加相应的js文件，并修改 src/playform/index.js
 */
const selector = {
  "jianshu.com": "._2rhmJa",
  "www.jianshu.com": "._2rhmJa",
  "juejin.cn": ".markdown-body",
  "blog.csdn.net": "#content_views",
  "segmentfault.com": ".article.fmt.article-content",
  "mp.weixin.qq.com": "#js_content",
  "zhuanlan.zhihu.com": ".Post-RichText",
  "www.news.cn": "#detail",
  "people.com.cn": ".rm_txt_con",
  "sspai.com": ".wangEditor-txt",
  "cnblogs.com": "#cnblogs_post_body",
  [myDomain]: "article.md-text",
  localhost: "article.md-text",
  copyHelper: ".markdown-body",
  "ruanyifeng.com": "#main-content",
};

// 记住用户上次选择的主题
let themeId = localStorage.getItem("copy_tool_themeId") || "1";

const hostname = window.location.hostname;

let hostKey = "";
for (const key in processDocument) {
  if (hostname.includes(key)) {
    hostKey = key;
    break;
  }
}

function isSite(kw) {
  return hostKey.includes(kw);
}

// dev copy helper
if (sessionStorage.isCopyHelperHost) {
  hostKey = "copyHelper";
}

function isCoolfePages() {
  return hostKey === myDomain && location.pathname.startsWith("/coolfe");
}

function isLocalHexoSite() {
  return hostKey.includes("localhost") && location.port * 1 === hexoPort;
}

function isRemoteHexoSite() {
  return hostKey === myDomain && !location.pathname.startsWith("/coolfe");
}

function isHexoSite() {
  return isLocalHexoSite() || isRemoteHexoSite();
}

function getBlogSelector() {
  const selectorStr = isCoolfePages()
    ? ".wrapper > header + section"
    : selector[hostKey] || ".markdown-body";
  logger.log("selector:", selectorStr);
  return selectorStr;
}

function appendThemeStyle(id) {
  Array.from(getAllEles(".toolbox-option")).forEach((node) => {
    node.className = "toolbox-option";
    if (node.dataset.id === themeId) {
      node.className = "toolbox-option active";
    }
  });
  const currentTheme = themes.find((item) => item.themeId === id);
  let themeStyle = getEle("#theme-style");
  if (!themeStyle) {
    themeStyle = mkEle("style");
    themeStyle.id = "theme-style";
    themeStyle.innerHTML = currentTheme.css;
    document.head.appendChild(themeStyle);
  } else {
    themeStyle.innerHTML = currentTheme.css;
  }
}

function initUI() {
  const tpl = `
<button class='side-btn cp-btn' type="button">拷贝助手</button>
<div class="preview-article-wrap cp-modal-wrapper" style="height: 100vh;display:none">
  <div class="preview-article-mask mark"></div>
  <div class="preview-phone-wrapper">
    <div class="preview-phone-model preview-phone-model_5_8">
      <div class="preview-article-wrapper preview-article-wrapper_5_8">
        <div class="preview-article-title">
          <div class="preview-article-title-left">
            <img
              src=""
              style="width: 7px" />
          </div>
          <div class="preview-article-title-center"></div>
          <div class="preview-article-title-right">
            <img
              src=""
              style="width: 17px" />
          </div>
        </div>
        <div class="preview-article-article preview-article-article_5_8">
          <div class="preview-article-article-header">
            <div class="rich_media_title" id="js-title"></div>
            <div id="meta_content" class="rich_media_meta_list">
              <span id="js-author" class="rich_media_meta rich_media_meta_text"></span>
              <span class="rich_media_meta rich_media_meta_nickname" id="profileBt" wah-hotarea="click">
                <a href="javascript:void(0);" class="wx_tap_link js_wx_tap_highlight weui-wa-hotarea" id="js_name">JS酷</a>
              </span>
              <em id="publish_time" class="rich_media_meta rich_media_meta_text">2022-01-26 19:02</em>
            </div>
          </div>
          <div id="nice" class="${hostKey.replace(/[.:]/g, "_")}"></div>
        </div>
      </div>
    </div>
    <div class="preview-toolbox">
      <div class="copy-btn tool-item">
        <div class="tool-item-label">复制:</div>
        <div>
          <div class="cp-btn js-copy-wechat">复制到微信</div>
          <div class="cp-btn js-copy-md">复制Markdown</div>
        </div>

      </div>

      <div class="tool-item">
        <div class="tool-item-label">下载:</div>
        <div>
          <div class="cp-btn js-download-md">下载 Markdown</div>
        </div>
      </div>
      <div class="tool-item">
        <div class="tool-item-label">排版:</div>
        <div>
          <a target="_black" href="https://editor.runjs.cool/" class="cp-btn">mdx-editor</a>
          <a target="_black" href="https://editor.mdnice.com/" class="cp-btn">mdnice</a>
        </div>
      </div>
      <div class="tool-item">
        <div class="tool-item-label">主题:</div>
        <div class="toolbox-select">

          <!-- theme-list-holder -->

        </div>
      </div>
      <div class="tool-item">
        <div>
          <div class="toolbox-hint">
            初始版本由<span class="toolbox-hint-highlight">JS酷</span>开发.
          </div>
        </div>
      </div>
    </div>
  </div>
  <div style="position: absolute; top: 20px; right: 20px">
    <img class="close-btn"
      src=""
      width="38" height="38" />
  </div>
</div>
  `;

  const themesHtml = themes
    .filter((theme) => theme.name === "简洁")
    .map(
      (theme) =>
        `<div class="toolbox-option " data-id=${theme.themeId}>${theme.name}</div>`
    )
    .join("");
  const template = tpl.replace(/<!-- theme-list-holder -->/m, themesHtml);

  // __NUXT__.state.view.column.entry.article_info.mark_content

  const cpToolDiv = mkEle("div");
  cpToolDiv.innerHTML = template;
  cpToolDiv.className = "cp-tool";

  // when develop locally, if not hexo site, disable this plugin
  if (
    location.hostname === "localhost" &&
    !localhostPorts.includes(location.port * 1)
  ) {
    return;
  }

  document.body.appendChild(cpToolDiv);
}

const getTitle = () => {
  const h1 = getAllEles("h1")[getAllEles("h1")?.length - 1];
  if (isSite('juejin')) {
    return getEle('.article-title').innerText;
  }
  return h1?.innerText || document.title;
};

function getMd() {
  let md = get(
    window,
    "__NUXT__.state.view.column.entry.article_info.mark_content"
  );
  // const blogSelector = getBlogSelector();
  // const blogEle = getEle(blogSelector);
  // const nodeConetnt = blogEle.cloneNode(true);

  const niceElement = getEle("#nice");
  const nodeConetnt = niceElement.cloneNode(true);

  // 各个站点执行自己的dom处理逻辑
  processDocument[hostKey] && processDocument[hostKey](nodeConetnt);

  // 复制文章节点，用turndown把html转换为markdown
  md = turndown(nodeConetnt.innerHTML);

  if (!isHexoSite()) {
    md = `# ${getTitle()}` + '\n' + md;
  }
  return md;
}

function replaceOutsideSpaces(html) {
  const startTagRe = /<(\w+)[^>]*?>/;
  const selfCloseTags = ["br", "img", "hr", "link", "meta"];
  let newHtml = "";
  let index = 0;
  let maxIndex = html.length - 1;
  while (index < maxIndex) {
    const startTagMatch = startTagRe.exec(html);
    if (startTagMatch) {
      const tagName = startTagMatch[1].toLowerCase();
      const tagRe = selfCloseTags.includes(tagName)
        ? new RegExp(`<${tagName}[^>]*?>`)
        : new RegExp(`<${tagName}[^>]*?>(.*?)</${tagName}>`);
      const tagMatched = tagRe.exec(html);
      newHtml += html.slice(0, tagMatched.index).replace(/\s/g, "&nbsp;");
      newHtml += html.slice(
        tagMatched.index,
        tagMatched.index + tagMatched[0].length
      );
      index = tagMatched.index + tagMatched[0].length;
      html = html.slice(index);
    } else {
      newHtml += html.replace(/\s/g, "&nbsp;");
      break;
    }
  }
  return newHtml;
}

function trimQueryString(url) {
  return url.replace(/\?.*$/, "");
}

function correctHnTags(els) {
  if (!isSite('juejin')) return els;
  const hTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];

  const hList = []
  els.forEach((el, index) => {
    if (!hTags.includes(el.tagName.toLowerCase())) return;

    if (!hList.length) {
      const item = { el, index, parent: null }
      hList.push(item);
      return;
    }
    const lastItem = hList[hList.length - 1];
    const lastLv = parseInt(lastItem.el.tagName.slice(1));
    const curLv = parseInt(el.tagName.slice(1));
    if (curLv === lastLv) {
      const item = { el, index, parent: lastItem.parent }
      hList.push(item);
    } else if (curLv > lastLv) { // child
      const item = { el, index, parent: lastItem }
      hList.push(item);
    } else { // parent
      let cItem = lastItem;
      let sameLevelItem = null;
      while (cItem.parent) {
        cItem = cItem.parent;
        const cLv = parseInt(cItem.el.tagName.slice(1));
        if (curLv === cLv) {
          sameLevelItem = cItem;
          break;
        }
      }

      if (!sameLevelItem) {
        throw new Error("h标签层级错误");
      }
      const item = { el, index, parent: sameLevelItem.parent }
      hList.push(item);
    }
  })

  const hTree = { root: true, sons: [] };
  hList.forEach(item => {
    if (!item.parent) {
      hTree.sons.push(item);
      return;
    }
    const parent = item.parent;
    parent.sons = parent.sons || [];
    parent.sons.push(item);
  })

  // top level should be h2
  // parent level should be one level higher than its children
  function changeChildrenLevel(node) {
    const newLevel = node.root ? 2 : parseInt(node.el.tagName.slice(1)) + 1;
    node.sons.forEach(child => {
      child.el = changeTitleLevel(child.el, newLevel);
      if (child.sons && child.sons.length) {
        changeChildrenLevel(child);
      }
    })
  }


  changeChildrenLevel(hTree);

  function updateEleList(node) {
    node.sons.forEach(child => {
      els[child.index] = child.el;
      if (child.sons && child.sons.length) {
        updateEleList(child)
      }
    })
  }

  // console.log('old els:', els.filter(el => el.tagName.toLowerCase().startsWith('h')));
  // console.log(hTree, '<--------hTree');
  updateEleList(hTree);
  // console.log('new els:', els.filter(el => el.tagName.toLowerCase().startsWith('h')));

  return els;
}



function changeTitleLevel(hEle, level) {
  const html = hEle.outerHTML;
  const newHTML = html.replace(/<h(\d)\s|<\/h(\d)>/g, (m, n1, n2) => {
    return m.replace(n1 || n2, level);
  })
  const div = document.createElement("div");
  div.innerHTML = newHTML;
  return div.firstElementChild;
}

function handleClick(e) {
  const target = e.target;
  // 点击 拷贝助手
  if (target.className.includes("side-btn")) {
    getEle(".cp-modal-wrapper").style.display = "block";
    const selectorStr = getBlogSelector();
    let blogEleChildren = getEle(selectorStr).cloneNode(true).children;
    blogEleChildren = correctHnTags(Array.from(blogEleChildren));
    getEle("#nice").innerHTML = "";
    const markdownBody = mkEle("div");

    // 原文出处
    const originDiv = mkEle("div");
    originDiv.innerHTML = `<blockquote class="article-from" style="word-break: break-all!important; margin-bottom: 20px; padding: 10px ; background: #f0f0f0; white-space:nowrap; overflow: hidden; text-overflow: ellipsis;">
<div style="width:1200px; white-space: nowrap!important; max-width: 1200px!important;">转载自: <a href="${trimQueryString(location.href)
      }" style=" margin-left: 5px; color: #999; font-size: 14px; font-weight: 300;" target="_blank">${trimQueryString(location.href)}</a></div>
</blockquote>`;

    if (!isHexoSite()) {
      markdownBody.appendChild(originDiv.firstElementChild);
      originDiv.remove();
    }

    Array.from(blogEleChildren).forEach((blogEleChildNode) => {
      if (
        ["H1", "H2", "H3", "H4", "H5", "H6"].includes(blogEleChildNode.tagName)
      ) {
        blogEleChildNode.innerHTML = `<span class="prefix"></span><span class="content">${blogEleChildNode.textContent}</span><span class="suffix"></span>`;
      }
      if (["BLOCKQUOTE"].includes(blogEleChildNode.tagName)) {
        blogEleChildNode.className = "multiquote-1";
      }

      if (blogEleChildNode.tagName == "PRE") {
        blogEleChildNode.className = "custom";

        if (blogEleChildNode.children && blogEleChildNode.children[0]) {
          // if <pre> first child is not <code>, then create <code> and append it
          if (blogEleChildNode.children[0].tagName !== "CODE") {
            const code = mkEle("code");
            code.append(...blogEleChildNode.children);
            blogEleChildNode.appendChild(code);
          } else {
            // 掘金去除复制按钮
            [...blogEleChildNode.children[0].children].forEach((l) => {
              if (l.innerHTML == "复制代码") {
                l.innerHTML = "";
              }
            });
          }
        }
      }
      if (blogEleChildNode.tagName !== "STYLE") {
        markdownBody.appendChild(blogEleChildNode);
      }
    });
    const h1 = getAllEles("h1")[getAllEles("h1")?.length - 1];

    const author = getEle(".name");
    // logger.log(author);

    getEle("#js-title").innerHTML = h1 ? h1.innerText : document.title;
    if (isSite('juejin')) {
      getEle("#js-title").innerHTML = getEle('.article-title').innerText;
    }
    getEle("#js-author").innerHTML = author ? author.innerText : "作者";
    // 预览文章
    getEle("#nice").appendChild(markdownBody);

    document.body.style.overflow = "hidden";

    if (isHexoSite()) {
      const footerDiv = getEle("#nice .article-footer");
      footerDiv && footerDiv.remove();

      // figure.highlight内部 用 pre 替代 父容器 table
      [...getAllEles("#nice figure.highlight")].forEach((figureEle) => {
        const preEle = figureEle.querySelector("td.code  pre");
        figureEle.innerHTML = "";
        figureEle.appendChild(preEle);
      });
    }
  }

  // 点击 遮罩或关闭按钮
  if (
    target.className.includes("preview-article-mask") ||
    target.className.includes("close-btn")
  ) {
    getEle(".cp-modal-wrapper").style.display = "none";
    document.body.style.overflow = "";
  }

  // 点击 不同主题
  if (target.className === "toolbox-option") {
    themeId = target.dataset.id;
    localStorage.setItem("copy_tool_themeId", themeId);
    appendThemeStyle(themeId);
  }

  // 点击 复制到微信 按钮
  if (target.className.includes("js-copy-wechat")) {
    const currentTheme = themes.find((item) => item.themeId === themeId);
    const mdcss = currentTheme.css;

    const element = getEle("#nice");
    const el = element.cloneNode(true);

    // 各个站点执行自己的dom处理逻辑
    processDocument[hostKey] && processDocument[hostKey](el);

    solveWeChatMath(el);
    let html = solveHtml(el, mdcss, hostKey);
    if (isHexoSite()) {
      html = html.replace(/<figure/g, "<div").replace(/<\/figure>/g, "</div>");
    }

    // <code class="hljs xxx"></code>
    // 把code.hljs内部不在标签内的空格转换为 &nbsp;
    html = html.replace(
      /(<code class="hljs[^"]*"[^>]*?>)([\s\S]*?)(<\/code>)/g,
      (match, p1, p2, p3) => {
        return p1 + replaceOutsideSpaces(p2) + p3;
      }
    );

    copySafari(html);
    alert("复制成功，请到微信公众平台粘贴");
  }

  // 点击 复制markdown 按钮
  if (target.className.includes("js-copy-md")) {
    let md = getMd();
    copySafari(md);
    alert("复制 markdown 成功");
  }

  // 点击  下载markdart 按钮
  if (target.className.includes("js-download-md")) {
    const md = getMd();
    const h1 = getEle("h1");
    const title = h1 ? h1.innerText + ".md" : "H1 is null.md";
    downLoad(title, md);
  }
}

function getLogger() {
  return {
    log(...args) {
      console.log("[KK-Copy-Helper Info]:", ...args);
    },
    warn(...args) {
      console.log("[KK-Copy-Helper Warn]:", ...args);
    },
    error(...args) {
      console.log("[KK-Copy-Helper Error]:", ...args);
    },
  };
}

function getEle(selector) {
  return document.querySelector(selector);
}

function getAllEles(selector) {
  return document.querySelectorAll(selector);
}

function mkEle(tag) {
  return document.createElement(tag);
}

const logger = getLogger();
function start() {
  initUI();
  appendThemeStyle(themeId);
  document.addEventListener("click", handleClick);
}

start();
